using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Collections;
using System.Text;

namespace TodoSoft.Json
{
	public class JsonSerializer
	{
		public static JsonSerializer Instance = new JsonSerializer();
		
		Dictionary<Type, object> _methods = new Dictionary<Type, object>();
		
		public ConvertDelegate<T> GetSerializeMethod<T>()
		{
			lock(_methods)
			{
				if(_methods.ContainsKey(typeof(T)))
					return (ConvertDelegate<T>)_methods[typeof(T)];
			
				var ilBuilder = new SerializeIlBuilder();
				ConfigureConvertionMethods(ilBuilder, new[]{typeof(T)});
			
				var method = ilBuilder.GetConvertionMethod<T>();
				_methods.Add(typeof(T), method);
				return method;
			}
		}
		
		public string Serialize<T>(T obj)
		{
			var sb = new StringBuilder(512);
			var sw = new StringWriter(sb);
			Serialize(obj, sw);
			return sb.ToString();
		}
		
		public void Serialize<T>(T obj, TextWriter textWriter)
		{
			var method = GetSerializeMethod<T>();
			method(obj, textWriter);
		}
		
		public void Clear()
		{
			lock(_methods)
			{
				_methods.Clear();
				_generatedTypes.Clear();
			}
		}
		
		#region TypeConfiguring
		class Comma
		{
			private readonly ISerializeMethodBuilder _methodBuilder;
			private bool _alwaysComma;
			private short isFirstPropVarName = -1;

			public Comma(ISerializeMethodBuilder methodBuilder)
			{
				_alwaysComma = false;
				_methodBuilder = methodBuilder;
			}

			public void PutIfNeed(string text)
			{
				if (!_alwaysComma)
				{
					if (isFirstPropVarName < 0)
					{
						isFirstPropVarName = _methodBuilder.CreateStringVar("");
					}

					_methodBuilder.EchoVar(isFirstPropVarName);
					_methodBuilder.EchoText(text);
				}
				else
				{
					_methodBuilder.EchoText("," + text);
				}
			}

			public void Put(string text)
			{
				if (_alwaysComma)
				{
					_methodBuilder.EchoText("," + text);
				}
				else
				{
					_methodBuilder.EchoText(text);
					_alwaysComma = true;
				}
			}

			public void SetIfNeed()
			{
				if (!_alwaysComma)
				{
					_methodBuilder.SetVar(isFirstPropVarName, ",");
				}
			}
		}

		List<Type> _generatedTypes = new List<Type>();
		
		private void ConfigureConvertionMethods(ISerializeTypeBuilder typeBuilder, IEnumerable<Type> types)
		{
			var subTypes = new List<Type>();
			
			foreach(var type in types)
			{
				typeBuilder.ConfigureConvertionMethod(type, method => {
					method.EchoText("{");
				
				    var commas = new Comma(method);

					foreach(var property in type.GetProperties())
					{
						if (IsToStringable (property.PropertyType)) //Простые типы
							WritePrimitiveFieldOrProp(property.PropertyType, property.Name, method, commas);
					}

					foreach (var property in type.GetFields())
					{
						if (IsToStringable (property.FieldType)) //Простые типы
							WritePrimitiveFieldOrProp(property.FieldType, property.Name, method, commas);
					}
				
					foreach(var property in type.GetProperties())
					{
						WriteFieldOrProp(property.PropertyType, property.Name, subTypes, method, commas);
					}

					foreach (var fieldInfo in type.GetFields())
					{
						WriteFieldOrProp(fieldInfo.FieldType, fieldInfo.Name, subTypes, method, commas);
					}

					method.EchoText("}");
				});
				_generatedTypes.Add(type);
			}
			
			if(subTypes.Count > 0)
				ConfigureConvertionMethods(typeBuilder, subTypes.Where(m=>!_generatedTypes.Contains(m)));
		}

		static bool IsToStringable (Type type)
		{
			return type.IsPrimitive || 
				type.IsEnum || 
				type == typeof(DateTime) ||
				type == typeof(Guid);
		}

		private static void WritePrimitiveFieldOrProp(Type type, string name, ISerializeMethodBuilder method, Comma commas)
		{
			commas.Put(name + ":\"");
			method.WriteProperty(name);
			method.EchoText("\"");
		}

		private static void WriteFieldOrProp(Type type, string name, List<Type> subTypes, ISerializeMethodBuilder method, Comma commas)
		{
			if (type == typeof (string)) //Строки
			{
				method.IfNotNull(name, iff =>
				{
					commas.PutIfNeed(name + ":\"");
					iff.WriteProperty(name);
					method.EchoText("\"");
					commas.SetIfNeed();
				});
			}
			else if (typeof (IEnumerable).IsAssignableFrom(type)) //Коллекции
			{
				method.IfNotNull(name, iff =>
					{
						commas.PutIfNeed(name + ":[");

						var firstVarName = iff.CreateStringVar("");

						iff.Foreach(name, (ffor, itemName) =>
							{
								ffor.EchoVar(firstVarName);
								ffor.EchoText("\"");
								ffor.EchoVar(itemName);
								ffor.EchoText("\"");
								iff.SetVar(firstVarName, ",");
							});

						iff.EchoText("]");

						commas.SetIfNeed();
					});
			}
			else if (!IsToStringable(type)) //Классы и структуры
			{
				subTypes.Add(type);

				Action<ISerializeMethodBuilder> action = a =>
				{
					commas.PutIfNeed(name + ":");
					a.CallConvertionMethod(name);
					commas.SetIfNeed();
				};

				if (type.IsClass)
					method.IfNotNull(name, action);
				else
					action(method);
			}
		}

		#endregion
	}
}

